(Rust) Zeroize memory
tl;dr
Use zeroize crate to zeroize sensitive data allocated in heep.
seed, secret key, mnemonic, password, randomness
Why zeroizing memory is needed
Side-Channel Security
Forward Secrecy
Rustの言語仕様(Drop trait)でaccidentalにstackやheapに残っている値が読み取られるのは防がれている。しかし、外部プログラム(例えば、デバッガーや外部物理マシン)がそれらのleftover valuesを読み取ることができる可能性がある。
OpenSSLのHeartbleed Bugはまさにこれ。
コンパイラの最適化によりclearning codeが省略されたりreorderedされる可能性がある。(Memory leaks are considered memory safe)
メモリ安全とは、メモリの範囲外アクセスや二重解放、ヌル参照、未初期化領域へのアクセスがない状態。 ただし、Rust の言うメモリ安全とは、メモリリークをしないことを保証するものではない。
e.x. Rc/Arc, mem::forget, Box::into_raw
https://gyazo.com/8563847d06f5e44876413283200a5dfd
How to zero a buffer
remote code executionやremotelyにuninitializedなmemoryをreadする脆弱性を防ぐ
OpenSSLのHeartbleed Bugなど
Heartbleed bug による秘密鍵漏洩の現実性について
OpenSSLの脆弱性(CVE-2014-0160)関連の情報をまとめてみた
Preventing compiler optimizations
コンパイラの最適化により、
dataのclearningを省略する可能性
呼び出された関数をインライン化しclearing codeをstackする可能性
Windows: SecureZeroMemory
Linux: memzero_explicit
More great references
https://www.youtube.com/watch?v=cQ9wTyYCdNU
Libraries
Securely zero memory while avoiding compiler optimizations.
Helpers for clearing sensitive data on the stack and heap
Replace ClearOnDrop with Zeroize
code:1.rs
fn zeroize_hack<Z: Default>(z: &mut Z) {
use core::{ptr, sync::atomic};
unsafe { ptr::write_volatile(z, Z::default()); }
atomic::compiler_fence(atomic::Ordering::SeqCst);
}
Findings in some audit reports
https://gyazo.com/1770c5f401375d940f18ccd34d41348b
impl Zeroize for PublicKey
We do not require this, and we do not make PubLicKey be Drop, but someone might want this for something.
In curv
https://gyazo.com/da9a35f63951e26f9ce4a6667f1b645c
Zeroize
code:1.rs
impl Zeroize for MiniSecretKey {
fn zeroize(&mut self) {
super::zeroize_hack(self);
}
}
impl Drop for MiniSecretKey {
fn drop(&mut self) {
self.zeroize();
}
}
dalek
implement Zeroize for Scalar and MontgomeryPoint
no_std support appears to be broken
Hello Rust 2018 + Zeroize Flag For simple support
Grin
Fill BlindingFactor with zeros on Drop
code:test_for_zeroize.rs
// This tests cleaning of BlindingFactor (e.g. secret key) on Drop.
// To make this test fail, just remove Zeroize derive from BlindingFactor definition.
fn blinding_factor_clear_on_drop() {
// Create buffer for blinding factor filled with non-zero bytes.
let ptr = {
// Fill blinding factor with some "sensitive" data
let bf = BlindingFactor::from_slice(&bf_bytes..); bf.0.as_ptr()
// -- after this line BlindingFactor should be zeroed
};
// Unsafely get data from where BlindingFactor was in memory. Should be all zeros.
let bf_bytes = unsafe { from_raw_parts(ptr, SECRET_KEY_SIZE) };
// There should be all zeroes.
let mut all_zeros = true;
for b in bf_bytes {
if *b != 0x00 {
all_zeros = false;
}
}
assert!(all_zeros)
}
Fill SecretKey with zeros on Drop for security
Clear on Drop
code:1.rs
impl Drop for FieldElement {
fn drop(&mut self) {
self.value.w.clear();
}
}
code:1.rs
/// Overwrite secret key material with null bytes when it goes out of scope.
impl Drop for SecretKey {
fn drop(&mut self) {
self.0.clear();
}
}
std/core crate
std::mem::forget
意図的なメモリリーク。(Memory leaks are memory safe!!)
memory leaks can be caused buy things like reference cycles and thread deadlock.
Rustのstd::mem::forgetがunsafeでない理由
Rustのforget関数
std::ptr::write_volatile (unsafe)
Performs a volatile write of a memory location with the given value without reading or dropping the old value.
Volatile operations are intended to act on I/O memory, and are guaranteed to not be elided or reordered by the compiler across other volatile operations.
src is moved into the location pointed to by dst
std::atomic::compiler_fence
与えられたstd::atomic::Orderingにしたがってコンパイラによるre-orderingを防ぐ。
Note that it does not prevent the hardware from doing such re-ordering.
std::atomic::Ordering::SeqCst: no re-ordering of reads and writes across this point is allowed.
std::mem::transmute (unsafe), std::Box::into_raw()
Rustオブジェクトの削除タイミングを手動で制御する
Rustで実行可能なメモリを確保
memo
the threat is not only leaking secrets from within the process, but also someone from the outside attaching a debugger, the whole process memory being dumped (a core dump, a VM snapshot, suspend-to-disk), or even direct access to the memory hardware (cold boot attack). If all traces of the secret had been safely erased by then, you're safe.
As I understand Laurent Simon's work, zeroing the secret data needed in cryptography can be cheap compared with the cost of doing the cryptography that uses it, but there are two bad situations one must avoid :
If all functions zero their own data, then we zero needlessly anytime a loop repeatedly calls the same functions, as always happens in cryptography.
Any data we zero but never touch results in cache misses, and squandered cache lines, as happens with the clear_on_drop crate's clear_stack_on_return function.
I'll close up this issue now because it takes the wrong approach. We do not want stack types that zero themselves per se. And boxing the secret data ala ClearOnDrop<Box<T>> does not work either unless you can manually tune specific workspaces that way.
Instead, we should store intermediate sensitive data in a stack-like way so that the same memory can easily be reused for different mathematical objects, but we need to zero exactly the used stack space when done with sensitive code, and take care with how else this stack gets used.
For this, we need instrumentation that tracks exactly how much stack these sensitive functions use, statically if possible, but dynamically when necessary, along with a volatile_zero_stack(bytes: usize) function to zero the exact quantity of stack space after use.
多くの中間値が生成される、key materialをstackに保持しsecret codeを使ったstackを全てzeroizeする必要。
You do not necessarily want to zero cryptographic key material when they types get dropped, as you might make many intermediate values. You often want to instrument your functions, keep key material only on the stack, and zero all the stack that you used when your cryptographic routines conclude.
This crate can be used to zero values from either the stack or the heap.
However, be aware several operations in Rust can unintentionally leave copies of data in memory. This includes but is not limited to:
Moves and Copy
Heap reallocation when using Vec and String
Borrowers of a reference making copies of the data
Pin can be leveraged in conjunction with this crate to ensure data kept on the stack isn't moved.
The Zeroize impls for Vec and String zeroize the entire capacity of their backing buffer, but cannot guarantee copies of the data were not previously made by buffer reallocation. It's therefore important when attempting to zeroize such buffers to initialize them to the correct capacity, and take care to prevent subsequent reallocation.
The secrecy crate provides higher-level abstractions for eliminating usage patterns which can cause reallocations: